package bma.common.langutil.objectquery;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import bma.common.langutil.objectquery.impl.IAccess;
import bma.common.langutil.objectquery.impl.IAccessMethod;
import bma.common.langutil.objectquery.impl.SAccessHut;

public class ObjectQuery {

	final org.slf4j.Logger log = org.slf4j.LoggerFactory
			.getLogger(ObjectQuery.class);

	protected class SAction {

		public Map<String, Object> result;
		public Object object;
		public ObjectQueryReq request;

		public SAction(Object object, ObjectQueryReq request,
				Map<String, Object> result) {
			super();
			this.object = object;
			this.result = result;
			this.request = request;
		}

	}

	public List<Map<String, Object>> query(List<Object> objList,
			ObjectQueryReq req) {
		int c = objList == null ? 0 : objList.size();
		List<Map<String, Object>> r = new ArrayList<Map<String, Object>>(c);
		if (objList != null) {
			List<SAction> actions = new ArrayList<ObjectQuery.SAction>(1);
			for (Object obj : objList) {
				Map<String, Object> m = new HashMap<String, Object>();
				r.add(m);
				createActions(actions, obj, req, m);
			}
			queryProp(actions);
		}
		return r;
	}

	public Map<String, Object> query(Object obj, ObjectQueryReq req) {
		Map<String, Object> r = new HashMap<String, Object>();
		List<SAction> actions = new ArrayList<ObjectQuery.SAction>(1);
		createActions(actions, obj, req, r);
		queryProp(actions);
		return r;
	}

	public ObjectQueryReq parseReq(Map<String, String> props) {
		ObjectQueryReq r = new ObjectQueryReq();
		buildReq(r, props);
		return r;
	}

	protected void buildReq(ObjectQueryReq r, Map<String, String> props) {
		for (Map.Entry<String, String> e : props.entrySet()) {
			String path = e.getKey();
			String[] plist = path.split("\\.");
			ObjectQueryReq work = r;
			for (int i = 0; i < plist.length; i++) {
				String p = plist[i];
				work = work.node(p);
			}
			if (plist.length > 0) {
				work.setAlias(e.getValue());
				work.setReturnValue(true);
			}
		}
	}

	protected void createActions(List<SAction> actions, Object obj,
			ObjectQueryReq req, Map<String, Object> result) {
		if (!req.hasProps())
			return;
		List<ObjectQueryReq> qlist = req.getProps();
		for (ObjectQueryReq nreq : qlist) {
			actions.add(new SAction(obj, nreq, result));
		}
	}

	protected void queryProp(List<SAction> actions) {
		if (actions == null)
			return;
		while (actions.size() > 0) {
			processQueryProp(actions);
		}
	}

	protected final static Object NULL = new Object();

	protected void processQueryProp(List<SAction> actions) {
		Map<String, Map<Object, List<SAction>>> loadActions = null;
		Iterator<SAction> it = actions.iterator();
		while (it.hasNext()) {
			SAction action = it.next();
			ObjectQueryReq req = action.request;
			Object obj = action.object;
			IAccess acc = getAccess(obj, req);

			if (acc != null) {
				Object val = acc.getValue(obj);
				if (val != null) {
					if (req.isReturnValue()) {
						action.result.put(req.getKey(), val);
					}
					if (req.hasProps()) {
						String otype = acc.getObjectType();
						if (loadActions == null) {
							loadActions = new HashMap<String, Map<Object, List<SAction>>>();
						}
						Map<Object, List<SAction>> vmap = loadActions
								.get(otype);
						if (vmap == null) {
							vmap = new HashMap<Object, List<SAction>>();
							loadActions.put(otype, vmap);
						}
						List<SAction> alist = vmap.get(val);
						if (alist == null) {
							alist = new ArrayList<SAction>();
							vmap.put(val, alist);
						}
						alist.add(action);
					}
				}
			} else {
				if (log.isDebugEnabled()) {
					log.debug(req.fullName() + " not accessable");
				}
			}
		}
		actions.clear();

		if (loadActions != null) {
			for (Map.Entry<String, Map<Object, List<SAction>>> e : loadActions
					.entrySet()) {
				String otype = e.getKey();
				Set<Object> valSet = e.getValue().keySet();
				if (valSet.size() == 1) {
					Object val = valSet.iterator().next();
					Object child = loadObject(otype, val);
					List<SAction> alist = e.getValue().get(val);
					processChild(actions, otype, val, child, alist);
				} else {
					Map<Object, Object> childs = loadObjects(otype, e
							.getValue().keySet());
					for (Map.Entry<Object, Object> e2 : childs.entrySet()) {
						Object pk = e2.getKey();
						List<SAction> alist = e.getValue().get(pk);
						Object child = e2.getValue();
						processChild(actions, otype, pk, child, alist);
					}
				}
			}
		}
	}

	protected void processChild(List<SAction> actions, String otype, Object pk,
			Object child, List<SAction> alist) {
		if (child == NULL) {
			if (log.isDebugEnabled()) {
				log.debug("loadObject({},{}) => null", otype, pk);
			}
		} else {
			if (child instanceof Collection) {
				Collection cl = (Collection) child;
				int cc = cl.size();
				for (SAction action : alist) {
					List<Map<String, Object>> childResult = new ArrayList<Map<String, Object>>(
							cc);
					action.result.put(action.request.getName(), childResult);

					Iterator it2 = cl.iterator();
					while (it2.hasNext()) {
						Object childObj = it2.next();
						Map<String, Object> m = new HashMap<String, Object>();
						childResult.add(m);
						createActions(actions, childObj, action.request, m);
					}
				}
			} else {
				for (SAction action : alist) {
					createActions(actions, child, action.request, action.result);
				}
			}
		}
	}

	protected Map<Class, SAccessHut> objectAccess = new HashMap<Class, SAccessHut>();

	protected IAccess getAccess(Object obj, ObjectQueryReq req) {
		Class cls = obj.getClass();
		SAccessHut hut = objectAccess.get(cls);
		if (hut == null) {
			hut = buildAccessHut(cls);
			objectAccess.put(cls, hut);
		}
		return hut.get(req.getName());
	}

	protected SAccessHut buildAccessHut(Class cls) {
		SAccessHut hut = new SAccessHut();
		Method[] ms = cls.getMethods();
		for (Method m : ms) {
			String mname = m.getName();
			OQKey a1 = (OQKey) m.getAnnotation(OQKey.class);

			Class[] ps = m.getParameterTypes();
			if (ps == null || ps.length == 0) {
				String pn = null;
				if (mname.startsWith("get") && mname.length() > 3) {
					pn = mname.substring(3, 4).toLowerCase()
							+ mname.substring(4);
					if (pn.equals("class"))
						continue;
				}
				if (pn == null && mname.startsWith("is") && mname.length() > 2) {
					pn = mname.substring(2, 3).toLowerCase()
							+ mname.substring(3);
				}
				if (pn != null) {
					IAccess acc = new IAccessMethod(
							a1 == null ? "" : a1.type(), m);
					if (log.isDebugEnabled()) {
						log.debug("{} [{},{}]", new Object[] { cls.getName(),
								pn, a1 == null ? "" : a1.alias() });
					}
					hut.put(pn, acc, a1 == null ? "" : a1.alias());
					continue;
				}
			}
		}
		return hut;
	}

	protected List<OQProvider> providers;

	public List<OQProvider> getProviders() {
		return providers;
	}

	public void setProviders(List<OQProvider> providers) {
		this.providers = providers;
	}

	public void addProvider(OQProvider p) {
		if (this.providers == null)
			this.providers = new ArrayList<OQProvider>();
		this.providers.add(p);
	}

	protected Object loadObject(String type, Object pk) {
		if (providers != null) {
			for (OQProvider p : providers) {
				Object r = p.loadObject(type, pk);
				if (r != null)
					return r;
			}
		}
		return null;
	}

	protected Map<Object, Object> loadObjects(String type, Set<Object> pk) {
		Map<Object, Object> r = new HashMap<Object, Object>();
		for (Object key : pk) {
			r.put(key, NULL);
		}
		if (providers != null) {
			for (OQProvider p : providers) {
				if (p.loadObjects(type, r)) {
					break;
				}
			}
		}
		return r;
	}
}
